//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Execution
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Threading;
    using System.Workflow.ComponentModel.Compiler;
    using System.Xml;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.Common;
    using Microsoft.Research.eResearch.Common.Eventing;
    using Microsoft.Research.eResearch.Common.Validation;

    /// <summary>
    /// Monitors job status from registry.  Whenever user changes the job status to pause, resume or stop it raises appropriate events.
    /// </summary>
    internal class JobMonitor : JobBase
    {
        /// <summary>
        /// Instance of the monitor thread.
        /// </summary>
        private Thread monitorThread;

        /// <summary>
        /// Initializes a new instance of the JobMonitor class.
        /// </summary>
        /// <param name="jobId">Job id to be monitored.</param>
        public JobMonitor(Guid jobId, bool isLightWeightExecution)
            : base(jobId, isLightWeightExecution)
        {
            this.WorkflowName = this.ActivityInstance.Activity.Name;

            // Monitor thread created.
            this.monitorThread = new Thread(this.MonitorRegistryJob);
        }

        /// <summary>
        /// Raised whenever user requested for pause.
        /// </summary>
        public event EventHandler<JobStatusEventArgs> PauseRequested;

        /// <summary>
        /// Raised whenever user requested for resume.
        /// </summary>
        public event EventHandler<JobStatusEventArgs> ResumeRequested;

        /// <summary>
        /// Raised whenever user requested for terminate.
        /// </summary>
        public event EventHandler<JobStatusEventArgs> StopRequested;

        /// <summary>
        /// Gets the workflow name.
        /// </summary>
        public string WorkflowName { get; private set; }

        /// <summary>
        /// Gets the activity instance of the current job.
        /// </summary>
        public ActivityInstance ActivityInstance
        {
            get { return this.WorkflowJob.ActivityInstances[0]; }
        }

        /// <summary>
        /// Start the job monitoring.
        /// </summary>
        public void Start()
        {
            ExceptionHandler.Handle(
            PolicyName.LoggingPolicy,
            () =>
            {
                WorkflowJob.Refresh();
                if (WorkflowJob.Status == JobStatus.StartPending)
                {
                    WorkflowJob.StartTime = DateTime.Now;
                    UpdateJobStatus(JobStatus.Running);
                }

                monitorThread.Start();
            });
        }

        /// <summary>
        /// Updates the job status in registry to Completed.
        /// </summary>
        public void Completed()
        {
            WorkflowJob.StopTime = DateTime.Now;
            UpdateJobStatus(JobStatus.Completed);
        }

        /// <summary>
        /// Updates the job status in registry to Aborted.
        /// </summary>
        /// <param name="errorMessage">The error message to be updated.</param>
        public void Terminated(string errorMessage)
        {
            if (!string.IsNullOrEmpty(errorMessage))
            {
                WorkflowJob.ErrorMessage = errorMessage;
            }

            WorkflowJob.StopTime = DateTime.Now;
            UpdateJobStatus(JobStatus.Aborted);
        }

        /// <summary>
        /// Updates the job status in registry to Paused.
        /// </summary>
        public void Paused()
        {
            UpdateJobStatus(JobStatus.Paused);
        }

        /// <summary>
        /// Blocks the calling thread until monitor thread terminates.
        /// </summary>
        public void JoinThread()
        {
            monitorThread.Join();
        }

        /// <summary>
        /// Updates the job status in the registry.
        /// </summary>
        /// <param name="status">Status to be updated.</param>
        private void UpdateJobStatus(JobStatus status)
        {
            lock (this)
            {
                ExceptionHandler.Handle(
                PolicyName.LoggingPolicy,
                () =>
                {
                    WorkflowJob.Status = status;
                    WorkflowJob.Save();
                });
            }
        }

        /// <summary>
        /// Monitor job status in registry. 
        /// </summary>
        private void MonitorRegistryJob()
        {
            JobStatus oldStatus = JobStatus.NotStarted;

            while (true)
            {
                ExceptionHandler.Handle(
                PolicyName.LoggingPolicy,
                () =>
                {
                    try
                    {
                        lock (this)
                        {
                            WorkflowJob.Refresh();
                        }

                        // To fire the event only one time whenever the status changes
                        if (oldStatus != WorkflowJob.Status)
                        {
                            oldStatus = WorkflowJob.Status;
                            switch (WorkflowJob.Status)
                            {
                                case JobStatus.StopPending:
                                    //// User terminated the job
                                    StopRequested.Fire(this, new JobStatusEventArgs(InternalJobStatus.StopRequested, WorkflowJob.ErrorMessage));
                                    break;

                                case JobStatus.PausePending:
                                    //// User paused the job
                                    PauseRequested.Fire(this, new JobStatusEventArgs(InternalJobStatus.PauseRequested));
                                    break;

                                case JobStatus.ResumePending:
                                    //// User resumed the job
                                    ResumeRequested.Fire(this, new JobStatusEventArgs(InternalJobStatus.ResumeRequested));
                                    break;
                            }
                        }
                    }
                    catch (BackendStorageException)
                    {
                        //// User terminated the job
                        StopRequested.Fire(this, new JobStatusEventArgs(InternalJobStatus.StopRequested, WorkflowJob.ErrorMessage));
                        throw;
                    }
                });

                // if the workflow completed or aborted then stop monitoring
                if (WorkflowJob.Status == JobStatus.Completed || WorkflowJob.Status == JobStatus.Aborted)
                {
                    break;
                }

                Thread.Sleep(500);
            }
        }
    }
}
